#include "tracker_query.h"
#include "exceptions.h"
#include "download.h"
#include "settings.h"

#include <netdb.h>
#include <sys/socket.h>
#include <sstream>
#include <unistd.h>
#include <iostream>

#define BUFFER_SIZE (1 << 16)

namespace torrent {

TrackerQuery::TrackerQuery(Download* d)
	:m_download(d),
	 m_port(0),
	 m_sockaddr(NULL),
	 m_state(STOPPED),
	 m_interval(15 * 60),
	 m_buf(NULL),
	 m_fd(-1)
{
}

TrackerQuery::~TrackerQuery() {
	clean();
}

void TrackerQuery::set(const std::string& uri,
		const std::string& infoHash,
		const Peer& me) {
	clean();

	// set
	m_uri = uri;
	m_infoHash = infoHash;
	m_interval = 15 * 60;
	m_me = me;

	if (m_infoHash.length() != 20)
		throw input_error("TrackerQuery ctor recived bad info hash");

	// TODO: Really shoud use sscanf instead.

	unsigned int p = uri.find("http://");

	if (p == std::string::npos)
		throw input_error("Tracker uri does not start with https://");

	p += 7;

	unsigned int
		col = uri.find(':', p),
		slash = uri.find('/', p);

	m_path = slash == std::string::npos || uri.length() == slash - 1 ?
			"index.html" :
			uri.substr(slash + 1, uri.length() + 1 - slash);

	if (slash == std::string::npos)
		slash = uri.length();

	m_hostname = uri.substr(p, std::min(col, slash) - p);

	if (col < slash) {
		 // Read a port
		std::stringstream str(uri.substr(col + 1, slash - col - 1));

		str >> m_port;

		if (str.fail())
			throw input_error("Tracker failed to parse port number");
	}else {
		m_port = 80;
	}

	if (m_port <= 0 || m_port >= (1 << 16))
		throw input_error("Tracker recived invalid port number");

	hostent* he = gethostbyname(m_hostname.c_str());

	if (he == NULL)
		throw input_error("Tracker failed to look up DNS address");

	m_sockaddr = new sockaddr_in;

	memset(m_sockaddr, 0, sizeof(sockaddr_in));
	memcpy(&m_sockaddr->sin_addr, he->h_addr_list[0], sizeof(in_addr));

	m_sockaddr->sin_family = AF_INET;
	m_sockaddr->sin_port = htons(m_port);
}

void TrackerQuery::sendState() {
	removeService();
	close();

	if (m_sockaddr == NULL)
		throw internal_error("Tried to send state without proper destination info");

	std::stringstream s;

	s << "GET /" << m_path
	  << "?info_hash=";

	escapeString(m_infoHash, s);

	if (m_me.dns().length())
		s << "&ip=" << m_me.dns();

	s << "&port=" << m_me.port()
	  << "&uploaded=" << m_download->m_bytesUploaded
	  << "&downloaded=" << m_download->m_bytesDownloaded
	  << "&left=" << (m_download->files().chunkCount() -
			  m_download->files().chunkCompleted()) *
			  m_download->files().chunkSize();

	switch(m_state) {
	case STARTED:
		s << "&event=started";
		break;
	case STOPPED:
		s << "&event=stopped";
		break;
	case COMPLETED:
		s << "&event=completed";
		break;
	default:
		break;
	}

	s << " HTTP/1.0\r\n"
	  << "Host: " << m_hostname << ":" << m_port << "\r\n"
	  << "User-Agent: " << Settings::httpName << "\r\n"
	  << "\r\n";

	if (s.fail() ||
		s.tellp() > BUFFER_SIZE)
		throw internal_error("Could not write tracker request message to buffer");

	// Connect to server, and stuff
	m_fd = makeSocket(m_sockaddr);
	m_buf = new char[BUFFER_SIZE];

	m_data = NULL;
	m_pos = m_length = s.readsome(m_buf, BUFFER_SIZE);

	insertWrite();
	insertExcept();
}

void TrackerQuery::escapeString(const std::string& src, std::ostream& stream) {
	stream << std::hex << std::uppercase;

	for (std::string::const_iterator itr = src.begin(); itr != src.end(); ++itr)
		stream << '%' << ((unsigned char)*itr >> 4) << ((unsigned char)*itr & 0xf);

	stream << std::dec << std::nouppercase;
}

void TrackerQuery::read() {
}

void TrackerQuery::write() {
}

void TrackerQuery::except() {
}


int TrackerQuery::fd() {
	return m_fd;
}

void TrackerQuery::close() {
	if (m_fd < 0)
		return;

	delete [] m_buf;
	m_buf = NULL;

	removeRead();
	removeWrite();
	removeExcept();

	::close(m_fd);

	m_fd = -1;
}

void TrackerQuery::clean() {
	close();

	delete m_sockaddr;
	m_sockaddr = NULL;
}

void TrackerQuery::state(State s, bool send) {
	m_state = s;

	if (send)
		sendState();
}

} // namespace torrent
